/**  
* Copyright (c) 2009, Noel Lynch All rights reserved. Redistribution and use  
* in source and binary forms, with or without modification, are permitted  
* provided that the following conditions are met: Redistributions of source  
* code must retain the above copyright notice, this list of conditions and the  
* following disclaimer. Redistributions in binary form must reproduce the above  
* copyright notice, this list of conditions and the following disclaimer in the  
* documentation and/or other materials provided with the distribution. Neither  
* the name of Noel Lynch nor the names of contributors may be used to  
* endorse or promote products derived from this software without specific prior  
* written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT  
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A  
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR  
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,  
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,  
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;  
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,  
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR  
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF  
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  
*/
// created by noellynch
// Sep 9, 2009

package com.sceneworker.app.tests;

import java.io.File;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.acarter.scenemonitor.SceneMonitor;
import com.jme.app.SimpleGame;
import com.jme.input.InputHandler;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Ray;
import com.jme.math.Triangle;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Line;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.TriMesh;
import com.jme.scene.Line.Mode;
import com.jme.scene.Spatial.LightCombineMode;
import com.jme.scene.state.GLSLShaderObjectsState;
import com.jme.system.DisplaySystem;
import com.jme.util.export.binary.BinaryImporter;
import com.jme.util.geom.BufferUtils;
import com.sceneworker.SceneWorker;
import com.sceneworker.app.ISceneWorkerToolsController;
import com.sceneworker.app.globals.SceneWorkerGlobals;

public class TestSSAO extends SimpleGame {

	private static final float MIN = 1f;
	private static final float MAX = 25f;
	private static final float LENGTH = 2f;
	private static final int DELTA = 30;
	private static final int DELTA_B = 30;
	final	static	public	int		RANGE_DEG = 90;
	
	ISceneWorkerToolsController		mcl_handler;
	GLSLShaderObjectsState		mcl_shader;
	
	boolean		mb_attrLoaded = false;
	
	public TestSSAO() {
	}
	
	private static final Logger logger = Logger
    .getLogger(GLSLShaderObjectsState.class.getName());
	
	public	TriMesh		make() {
		FloatBuffer		lcl_verts = BufferUtils.createFloatBuffer(new Vector3f(-5, 0, 0), new Vector3f(0, 0, 10), new Vector3f(5, 0, 0));
		FloatBuffer		lcl_normals = BufferUtils.createFloatBuffer(new Vector3f(0, -1, 0), new Vector3f(0, 0, -1), new Vector3f(0, 1, 0));
		IntBuffer		lcl_indices = BufferUtils.createIntBuffer(0, 1, 2);
		
		TriMesh			lcl_mesh = new TriMesh("TRIMESH", lcl_verts, lcl_normals, null, null, lcl_indices);
		return lcl_mesh;
	}
	
	@Override
	protected void simpleInitGame() {
		Spatial		object = null;
		
		try {
			logger.setLevel(Level.SEVERE);
			object = (Spatial)BinaryImporter.getInstance().load(new File("test/testObjects/corridorPart.jme"));
			//object = new Pyramid("pyramid", 5, 7);
			//object = new Quad("q", 5, 5);
			//Sphere			lcl_loaded = new Sphere("s", 5, 5, 5);
			//object = make();
			
			FloatBuffer		aoBuffer = null;
			if(object instanceof TriMesh) {
				aoBuffer = calculateAOBuffer((TriMesh)object);
			}
			
			mcl_shader = display.getRenderer().createGLSLShaderObjectsState();
			object.setRenderState(mcl_shader);
			mcl_shader.load(TestSSAO.class.getClassLoader().getResource(SceneWorkerGlobals.SCENEWORKER_SHADER_DIR + "simpleao/ao2.vert"), 
					TestSSAO.class.getClassLoader().getResource(SceneWorkerGlobals.SCENEWORKER_SHADER_DIR + "simpleao/ao2.frag"));
			
			if(aoBuffer != null) {
				mcl_shader.setAttributePointer("ssaoFactor", 1, true, 0, aoBuffer);
			}
			
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		SceneWorker.inst().initialiseSceneWorkerAndMonitor();
		SceneMonitor.getMonitor().registerNode(rootNode, "root");
		
		// initialise the application handler so we get tools palette, input handler and rendering
		//mcl_handler = new SceneWorkerAppController(this);
		//mcl_handler.initialise();
		
		rootNode.attachChild(object);
		rootNode.updateRenderState();
		rootNode.updateGeometricState(0, true);
		
	}
	
	public	FloatBuffer	calculateAOBuffer(TriMesh object) {
		Triangle		[]meshAsTris;
		meshAsTris = new Triangle[object.getTriangleCount()];
		object.getMeshAsTriangles(meshAsTris);
		
		System.out.println("starting hemisphere");
		FloatBuffer		vertexBuffer = object.getVertexBuffer();
		FloatBuffer		normalBuffer = object.getNormalBuffer();
		
		Vector3f		vertex = new Vector3f();
		Vector3f		normal = new Vector3f();
		
		vertexBuffer.rewind();
		normalBuffer.rewind();
		
		FloatBuffer		aoBuffer = BufferUtils.createFloatBuffer(vertexBuffer.capacity() / 3);
		
		// for each vertex
		for(int index = 0; index < vertexBuffer.capacity(); index += 3) {
			
			vertex.x = vertexBuffer.get(index);
			vertex.y = vertexBuffer.get(index + 1);
			vertex.z = vertexBuffer.get(index + 2);
			
			normal.x = normalBuffer.get(index);
			normal.y = normalBuffer.get(index + 1);
			normal.z = normalBuffer.get(index + 2);
			
			// get the ambient occlusion factor
			float	aoValue = hemisphereCalculate(vertex, normal, LENGTH, meshAsTris);
			aoBuffer.put((index / 3), aoValue);
		}
		
		aoBuffer.rewind();
		for(int i = 0; i < aoBuffer.capacity(); i++) {
			System.out.println("ao " + aoBuffer.get(i));
		}
		aoBuffer.rewind();
		
		return aoBuffer;
	}
	
	public	float	hemisphereCalculate(Vector3f a, Vector3f n, float rayLength, Triangle []meshAsTris) {
		Vector3f		nBent = new Vector3f();
		Quaternion		rot = new Quaternion();
		
		Vector2f		attempts = new Vector2f();
		
		for(int i = -RANGE_DEG; i <= RANGE_DEG; i += DELTA) {
			rot.fromAngleNormalAxis(-FastMath.DEG_TO_RAD * (float)i, Vector3f.UNIT_X);
			nBent = rot.mult(n);
			
			hemisphereCalculate2(a.add(n.mult(2)), nBent, rayLength, attempts, meshAsTris);
			//System.out.println(lcl_attempts.mf_hit + " " + lcl_attempts.mf_attempts);
		}
		
		return 1.0f - (attempts.x / attempts.y);
	}
	
	public	void	hemisphereCalculate2(Vector3f a, Vector3f nBent, float rayLength, Vector2f attempts, Triangle []meshAsTris) {
		Vector3f		b = new Vector3f();
		Vector3f		nBent2 = new Vector3f();
		Quaternion		rot = new Quaternion();
		
		Ray				ray = new Ray();
		Vector3f		intersection = new Vector3f();
		float			dist;
		
		ArrayList<Vector3f>			lcla_pts = new ArrayList<Vector3f>();
		ArrayList<ColorRGBA>		lcla_colours = new ArrayList<ColorRGBA>();
		
		Line			lcl_line;
		
		for(int li_delta = -RANGE_DEG; li_delta <= RANGE_DEG; li_delta += DELTA_B) {
			b.set(0, 0, 0);
			
			rot.fromAngleAxis(FastMath.DEG_TO_RAD * li_delta, Vector3f.UNIT_Y);
			nBent2 = rot.mult(nBent);
			nBent2.mult(rayLength, b);
			b.addLocal(a);
			
			ray.setOrigin(a);
			ray.setDirection(nBent2);
			
			boolean			hit = false;
			for(int triIndex = 0; (triIndex < meshAsTris.length) && (!hit); triIndex++) {
				Triangle	tri = meshAsTris[triIndex];
				if(ray.intersectWhere(tri, intersection)) {
					dist = intersection.distance(ray.getOrigin());
					
					if((dist > MIN) && (dist < MAX)) {
						hit = true;
					}
				}
			}
			
			if(hit) {
				attempts.x += 1f;
			}
			
			attempts.y += 1f;
			
			lcla_pts.add(a.clone());
			lcla_pts.add(b.clone());
			lcla_colours.add(hit ? ColorRGBA.red : ColorRGBA.blue);
			lcla_colours.add(hit ? ColorRGBA.red : ColorRGBA.blue);
		}
		
		Vector3f		lcla_pts2[] = new Vector3f[lcla_pts.size()];
		lcla_pts.toArray(lcla_pts2);
		ColorRGBA		lcla_colors2[] = new ColorRGBA[lcla_colours.size()];
		lcla_colours.toArray(lcla_colors2);
		
		lcl_line = new Line("LINE", lcla_pts2, null, lcla_colors2, null);
		lcl_line.setLightCombineMode(LightCombineMode.Off);
		lcl_line.setMode(Mode.Segments);
		rootNode.attachChild(lcl_line);
		
		/*Point		lcl_p = new Point("p", lcla_pts2, null, lcla_colors2, null);
		lcl_p.setLightCombineMode(LightCombineMode.Off);*/
		
	}
	
	public static void main(String[] args) {
		TestSSAO		lcl_main = new TestSSAO();
		lcl_main.setConfigShowMode(ConfigShowMode.AlwaysShow);
		lcl_main.start();
    }

	public Camera getCamera() {
		return cam;
	}

	public DisplaySystem getDisplaySystem() {
		return display;
	}

	public InputHandler getInputHandler() {
		return input;
	}

	public Node getRootNode() {
		return rootNode;
	}

	public float getTimePerFrame() {
		return tpf;
	}

	public void setInputHandler(InputHandler cl_ip) {
		input = cl_ip;
	}

	@Override
	protected void simpleRender() {
		//mcl_handler.render();
	}

	@Override
	protected void simpleUpdate() {
		/*if(!mb_attrLoaded) {
			System.out.println("setting attribute");
			mcl_shader.setAttributePointer("ssaoFactor", 1, true, 0, mcl_ao);
			
			for(ShaderVariable lcl_shader : mcl_shader.getShaderAttributes()) {
				System.out.println("attr " + lcl_shader.name);
			}
			
			mb_attrLoaded = true;
		}*/
		
		//mcl_handler.update();
	}
	
	

}
